package animator.phantom.gui.timeline;

/*
    Copyright Janne Liljeblad 2006,2007,2008

    This file is part of Phantom2D.

    Phantom2D is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Phantom2D is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Phantom2D.  If not, see <http://www.gnu.org/licenses/>.
*/

import animator.phantom.controller.*;
import animator.phantom.gui.*;
import animator.phantom.gui.AnimFrameGUIParams;
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;

//--- This class displays timeline scale.
//--- The name of class is very bad.
public class TimeLineDisplayPanel extends JPanel implements MouseListener, MouseMotionListener
{
	//--- SCALE DRAW PARAMETERS
	private int SCALE_DISPLAY_HEIGHT = 30;

	//--- Scale mark sizes
	private static final float SMALL_MARK_HEIGHT = 4;
	private static final int MEDIUM_MARK_HEIGHT = 7;
	private static final int BIG_MARK_HEIGHT = 17;
	private static final int CURRENT_FRAME_HEIGHT = 22;
	private static final int MARKER_Y = 2;
	private static final int MARK_X_OFF = 7;
	private static final int PREVIEW_H1 = 20;
	private static final int PREVIEW_H2 = 22;

	//--- Minimum amunt of pixels per unit for draw
	private static final int MIN_PIXELS_FOR_FRAME_DRAW = 7;
	private static final int MIN_PIXELS_FOR_SECOND_STEP = 50;
	
	private static final Color FRAME_POINTER_COLOR = new Color( 184, 51, 51 );
	private static final Color MARKED_RANGE_COLOR = new Color( 164, 167, 179 );//141, 144, 154 );
	private static final Color PREVIEW_AREA_COLOR = new Color( 65,160,60 );

	//--- Parameters used to deternimene how many numbers are used to express time.
	private static final int MAX_SECONS_FOR_4_NUMBER_READING = 60;
	private static final int MAX_SECONS_FOR_6_NUMBER_READING = 3600;

	//--- Frame pointer triangle
	private static final int[] tx = { -5,8,1 };
	private static final int[] ty = { 0,0,6 };

	//--- Prerendered images for timelines in differend scales and draw params.
	private BufferedImage[] timelineScaleImages;
	private static final int TIMECODE_Y = 17 ;
	private static final Font TIME_CODE_FONT = new Font("Monospaced", Font.PLAIN, 12 );
	
	//--- Timeline inMarker
	private BufferedImage inMarker;
	//--- Timeline out Marker
	private BufferedImage outMarker;
	//--- Timeline mark images
	private BufferedImage markImg = GUIResources.getResourceBufferedImage( GUIResources.timeLineMark );

	//--- Flag for on going adjustment. Used for draw optimization.
	private boolean dragInProgress = false;

	//-------------------------------------------- CONSTRUCTOR
	public TimeLineDisplayPanel()
	{
		createTimelineScaleImages();
		setPreferredSize( new Dimension( AnimFrameGUIParams.getTimeEditRightColWidth(),
						AnimFrameGUIParams.TE_SCALE_DISPLAY_HEIGHT) );

		//--- MouseListener, MouseMotionListener
		addMouseListener( this );
		addMouseMotionListener( this );
	}

	public boolean dragInProgress(){ return dragInProgress; }

	//----------------------------------------- MOUSE EVENTS
	//--- Handle mouse press on panel area
	public void mousePressed(MouseEvent e)
	{
		requestFocusInWindow();

		dragInProgress = true;
		PreviewController.stopPlayback();
	}

	//--- Handle mouse dragging
	public void mouseDragged(MouseEvent e)
	{
		int frame = getFrame( e.getX() );
		TimeLineController.setCurrentFrame( frame, this );
		UpdateController.updateMovementDisplayers();
		repaint();
	}
	//--- Notify clip and set clipBeingEdited to null
	public void mouseReleased(MouseEvent e)
	{
		dragInProgress = false;
		int frame = getFrame( e.getX() );
		TimeLineController.setCurrentFrame( frame, this );
		UpdateController.updateCurrentFrameDisplayers( false );
		repaint();
	}

	//--- Mouse events that are not handled.
	public void mouseClicked(MouseEvent e){}
	public void mouseEntered(MouseEvent e){}
	public void mouseExited(MouseEvent e){}
	public void mouseMoved(MouseEvent e){}

	private int getFrame( int x )
	{
		float pixPerFrame = TimeLineController.getCurrentScaleMultiplier();
		int xOffset = Math.round( pixPerFrame * TimeLineController.getTimeLinePosition() );
		int frame = Math.round( ( x + xOffset) / pixPerFrame );
		//--- clamp into movie length.
		if( frame < 0 ) frame = 0;
		if( frame > ( ProjectController.getLength() - 1 ) ) frame =  ProjectController.getLength() - 1;
		return frame;
	}

	//----------------------------------------- GRAPHICS
	public void paint( Graphics g )
	{
		//--- scale and position
		float pixPerFrame = TimeLineController.getCurrentScaleMultiplier();
		int timeLineScale = TimeLineController.getTimelineScale();
		int timeLinePos = TimeLineController.getTimeLinePosition();
		int xOffset = Math.round( pixPerFrame * timeLinePos );

		//--- Clear end of panel that may be exposed after scale image
		g.setColor( Color.white );
		g.fillRect( timelineScaleImages[ timeLineScale ].getWidth(), 0, 
				getWidth() - timelineScaleImages[ timeLineScale ].getWidth(), getHeight() );

		//--- Draw timeline scale
		g.drawImage( timelineScaleImages[ timeLineScale ], -xOffset, 0, null );

		//--- Draw user defined marks.
		Vector <Integer> marks = TimeLineController.getMarks();
		
		for( int i = 0; i < marks.size(); i++ )
		{
			int markFrame = marks.elementAt( i ).intValue();
			int markPos = Math.round( ( markFrame - timeLinePos ) * pixPerFrame );
			g.drawImage( markImg, markPos - MARK_X_OFF, 0, null );
			System.out.println("ffff");
		}

		//--- Draw preview area
		if( PreviewController.getStartFrame() != -1 )
		{
			int inX = Math.round( pixPerFrame * 
				( PreviewController.getStartFrame() - timeLinePos ) );
			int outX = Math.round( pixPerFrame * 
				( PreviewController.getEndFrame() - timeLinePos ) );
			g.setColor( PREVIEW_AREA_COLOR );
			g.fillRect( inX, PREVIEW_H1, outX - inX, PREVIEW_H2 );
		}

		//--- Draw frame pointer
		g.setColor( FRAME_POINTER_COLOR );
		int framePointerX = Math.round( ( TimeLineController.getCurrentFrame() - timeLinePos ) *
					pixPerFrame );
		g.fillRect(  framePointerX, 0, 3, CURRENT_FRAME_HEIGHT );
		int[] drawx = { framePointerX + tx[ 0 ], framePointerX + tx[ 1 ], framePointerX + tx[ 2 ] };
		g.fillPolygon( drawx, ty, 3 );
		g.setColor( Color.white );
		g.drawLine( framePointerX +1 , 0, framePointerX +1 , CURRENT_FRAME_HEIGHT );
	}
	
	
	//-------------------------------------------------------- TIMELINE SCALES CREATING
	//--- This needs to happen after frameMultipliers are created.
	public void createTimelineScaleImages()
	{		
		float[] frameMultipliers = TimeLineController.getFrameMultipliers();
		int framesInMovie = ProjectController.getLength();
		
		timelineScaleImages = new BufferedImage[ frameMultipliers.length ];
		for( int i =  0; i < frameMultipliers.length; i++ )
		{
			//--- Calculate size of timeline scale image strip for given scale
			int scaleLength = Math.round( framesInMovie * frameMultipliers[ i ] );
			//--- Create image
			BufferedImage addScale = new BufferedImage( 	scaleLength,
									SCALE_DISPLAY_HEIGHT,
									BufferedImage.TYPE_INT_ARGB );
			//--- Draw scale
			drawTimelineScale( addScale.createGraphics(), frameMultipliers[ i ], framesInMovie );
			timelineScaleImages[ i ] = addScale;
		}
		
	}//end createTimelineScaleImages

	//--- Draws image of timeline in given scale
	private void drawTimelineScale( Graphics2D g, float pixPerFrame, int movieLength )
	{
		//--- Length of required scale strip in pixels
		int scaleLength = Math.round( movieLength * pixPerFrame );
		
		//--- Paint background
		g.setColor( GUIColors.timeLineScaleColor );//new Color( 236, 240,250) 
		g.fillRect( 0, 0, scaleLength, 22 );
		
		//--- Create line object for draving
		Line2D.Float line = new Line2D.Float();	

		//--- Draw long vertical line
		g.setColor( Color.black );
		line.setLine(0, 0, scaleLength, 0);
		g.draw( line );

		//--- Draw end line
		line.setLine(scaleLength -1, 0, scaleLength - 1, SMALL_MARK_HEIGHT);
		g.draw( line );
		
		//--- Draw frame marks
		drawFrameMarks( g, pixPerFrame, movieLength );
		drawSecondMarks( g, pixPerFrame, movieLength, BIG_MARK_HEIGHT );
		drawTenFrameMarks( g, pixPerFrame, movieLength,  MEDIUM_MARK_HEIGHT );

		//--- Draw timecodes.
		drawTimeNumbers( g, pixPerFrame );
	} 
	
	//--- Draws small mark for each frame
	private void drawFrameMarks( Graphics2D g, float pixelsPerFrame, int framesInMovie )
	{
		//--- If frames come too close to each other, don't draw them.
		if( pixelsPerFrame < MIN_PIXELS_FOR_FRAME_DRAW ) return;

		//--- Draw frames.
		Line2D.Float line = new Line2D.Float();
		float drawX;
		float iFloat;
		for( int i = 0; i < framesInMovie; i++ )
		{
			iFloat = ( new Integer( i ) ).floatValue();
			drawX = iFloat * pixelsPerFrame;
			line.setLine(drawX, 0, drawX, SMALL_MARK_HEIGHT );
			g.draw( line );
		}
	}

	private void drawTenFrameMarks( Graphics2D g, float pixelsPerFrame,
						int framesInMovie, int mark_height )
	{
		//--- If frames come too close to each other, don't draw them.
		if( pixelsPerFrame < MIN_PIXELS_FOR_FRAME_DRAW ) return;

		//--- Draw ten frame marks.
		Line2D.Float line = new Line2D.Float();
		int framesPerSecond = ProjectController.getFramesPerSecond();
		int secondsInMovie = framesInMovie / framesPerSecond;
		float drawX;
		int drawFrame;
		
		for( int i = 0; i < secondsInMovie; i++ )
		{
			drawFrame = framesPerSecond * i + 10;
			drawX = pixelsPerFrame * drawFrame;
			line.setLine(drawX, 0, drawX, mark_height );
			g.draw( line );

			drawFrame = framesPerSecond * i + 20;
			drawX = pixelsPerFrame * drawFrame;
			line.setLine(drawX, 0, drawX, mark_height );
			g.draw( line );
		}
	}

	//--- Draws desired size mark for each second
	private void drawSecondMarks( Graphics2D g, float pixelsPerFrame,
					int framesInMovie, int mark_height )
	{
		Line2D.Float line = new Line2D.Float();
		int framesPerSecond = ProjectController.getFramesPerSecond();
		int secondsInMovie = framesInMovie / framesPerSecond;
		float drawX;
		int step = getSecondStep(pixelsPerFrame);
		for( int i = 0; i < secondsInMovie; i = i + step )
		{
			drawX = i * pixelsPerFrame * framesPerSecond;
			line.setLine(drawX, 0, drawX, mark_height );
			g.draw( line );
		}
	}

	private void drawTimeNumbers( Graphics2D g, float pixelsPerFrame )
	{
		//--- Determine how many numbers at most from end in form hh:mm:ss:ff are drawn
		int maxNumberCount;
		int framesPerSecond = ProjectController.getFramesPerSecond();
		int secondsInMovie = ProjectController.getLength() / framesPerSecond;
		if( secondsInMovie <  MAX_SECONS_FOR_4_NUMBER_READING ) maxNumberCount = 4;
		else if( secondsInMovie < MAX_SECONS_FOR_6_NUMBER_READING ) maxNumberCount = 6;
		else maxNumberCount = 8;

		//--- draw Time code for every second
		int x, frame, step;
		step = getSecondStep(pixelsPerFrame);
		for( int i = 0; i < secondsInMovie; i = i + step )
		{
			frame = i * framesPerSecond;
			x = Math.round( pixelsPerFrame * frame ) + 2;
			renderTimeCode( frame, g, x, maxNumberCount, framesPerSecond );
		}
	}

	private int getSecondStep(float pixelsPerFrame)
	{
		float framesForMinStep = MIN_PIXELS_FOR_SECOND_STEP / pixelsPerFrame;
		int step = (int) Math.floor(framesForMinStep / (float) ProjectController.getFramesPerSecond()) + 1;
		return step;
	}

	private void renderTimeCode( int frame,  Graphics2D g, int x, 
					int maxNumberCount, int framesPerSecond )
	{
		//--- Create timecode string.
		String timecode = parseTimeCodeString( frame, maxNumberCount, framesPerSecond );
		//--- Draw timecode
		g.setFont( TIME_CODE_FONT );
		g.drawString( timecode, x, TIMECODE_Y );
		
	}
	
	public static String parseTimeCodeString( int frame, int maxNumberCount, int framesPerSecond )
	{
		int hours = frame / ( 3600 * framesPerSecond );
		int mFrames = frame % ( 3600 * framesPerSecond );
		int minutes = mFrames / ( 60 * framesPerSecond );
		int sFrames = mFrames % ( 60 * framesPerSecond );
		int seconds = sFrames / framesPerSecond;
		int frames = sFrames % framesPerSecond;
	
		//--- Create time code String
		StringBuilder sb = new StringBuilder();
		if( maxNumberCount == 8 )
		{
			appendNumberPair( hours, sb );
			sb.append( ":" );
		}

		if( maxNumberCount >= 6 )
		{
			appendNumberPair( minutes, sb );
			sb.append( ":" );
		}

		appendNumberPair( seconds, sb );
		sb.append( ":" );

		appendNumberPair( frames, sb );
		
		return sb.toString();
	}


	//--- Append two digit value with leading zero if value < 100
	private static void appendNumberPair( int value, StringBuilder sb )
	{
		if( value < 10 )
		{
			sb.append( "0" );
			sb.append( value );
		}
		else sb.append( value );
	}

}//end class
